﻿// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "Engine/EngineTypes.h"
#include "AsyncSetTimer.generated.h"

/**
 *	[需求]
 *		若要同时处理N个对象的异步调用，引擎内置的蓝图节点是难以满足需求的，因此设计并实现一个具有这样功能的蓝图节点是十分必要的。
 *		Set Timer By Event支持Pause、Unpause、Clear等功能，也可以等效地被封装成一个可取消的Delay节点；
 *		但是由于SetTimerByEevent节点的Delegate是一个无参数的委托，因此在处理N个对象的时候，无法确定是哪一个对象完成了Delay过程。
 *	[设计]
 *		假设我们把Set Timer By Event节点的左侧输入引脚Event，移到节点右侧，变成一个输出执行流引脚Completed；
 *		并且在执行Completed引脚后的函数时，可以同时返回TimerHandle，那么就可以根据TimerHandle取得与之对应的Object，然后调用该Object的函数，完成同时处理N个对象的异步调用。
 *		在经过以上调整，新SetTimer节点的引脚应该是如下结构。
 **/
DECLARE_LOG_CATEGORY_EXTERN(LogAsyncAction, Log, All);

// 由于默认的执行流then输出引脚执行时并不能返回参数，在本例中几乎无任何作用，故使用UCLASS(meta =(HideThen =true))宏隐藏了默认的then执行流。
UCLASS(meta = (HideThen = true))
class MISCELLANEOUSPROJECT_API UAsyncSetTimer : public UBlueprintAsyncActionBase
{
	GENERATED_BODY()
public:
	UAsyncSetTimer();
	~UAsyncSetTimer();
public:
	/**
	 * 此处声明了该蓝图节点，BlueprintInternalUseOnly ="true"，隐藏自动生成的非异步蓝图节点，没有这个说明符将蓝图中看到二个同名节点。
	 * 此函数的返回值必须是该派生类类型的指针，该返回值参数不会出现在蓝图节中，函数体主要用于输入参数检验、创建派生类对象以及赋值。
	 * Set a timer to execute Completed delegate. Setting an existing timer will reset that timer with updated parameters.
	 * @param WorldContextObject		The world context.
	 * @param Time						How long to wait before executing the delegate, in seconds. Setting a timer to <= 0 seconds will clear it if it is set.
	 * @param bLooping					True to keep executing the delegate every Time seconds, false to execute delegate only once.
	 * @param InitialStartDelay			Initial delay passed to the timer manager, in seconds.
	 * @param InitialStartDelayVariance	Use this to add some variance to when the timer starts in lieu of doing a random range on the InitialStartDelay input, in seconds.
	 * @return							The timer handle to pass to other timer functions to manipulate this timer.
	 */
	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Set Timer", ScriptName = "SetTimerDelegate", AdvancedDisplay = "InitialStartDelay, InitialStartDelayVariance"), Category = "Utilities|Time")
	static UAsyncSetTimer* SetTimer(const UObject* WorldContextObject, float Time, bool bLooping, float InitialStartDelay = 0.f, float InitialStartDelayVariance = 0.f);

	/**
	 * 结束时，调用自定义版本的Clear and Invalidate Timer by Handle(Async)，在清除TimerHandle前，使与之对应的UAsync_SetTimer对象预备销毁。否则，易造成内存泄漏。
	 * Clears a set timer and set ready to destroy UAsyncSetTimer object
	 * @param Handle		The handle of the timer to clear.
	 */
	UFUNCTION(BlueprintCallable, meta = (DisplayName = "Clear and Invalidate Timer by Handle(Async)", ScriptName = "ClearAndInvalidateTimerHandle", WorldContext = "WorldContextObject"), Category = "Utilities|Time")
	static void ClearAndInvalidateTimerHandle(UObject* WorldContextObject, UPARAM(ref) FTimerHandle& Handle);


public: // Generate exec out pin

	// Delcear delegate for finished delay
	DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTimerHandleDelegate, FTimerHandle, TimerHandle);
	/** Generate Exec Outpin, named Then */
	UPROPERTY(BlueprintAssignable)
	FTimerHandleDelegate Then;

	/** Generate Exec Outpin, named Completed */
	UPROPERTY(BlueprintAssignable)
	FTimerHandleDelegate Completed;

private: // UBlueprintAsyncActionBase interface

	virtual void Activate() override;
	//~ END UBlueprintAsyncActionBase interface

	/** UFunction for Delegate.BindUFunction; */
	UFUNCTION()
	void CompletedEvent();

	/** Based on UKismetSystemLibrary::K2_SetTimerDelegate() */
	FTimerHandle SetTimerDelegate(FTimerDynamicDelegate Delegate, float Time, bool bLooping, float InitialStartDelay = 0.f, float InitialStartDelayVariance = 0.f);

private:
	const UObject* WorldContextObject;
	FTimerHandle TimerHandle;
};
